package com.example.mq.mqserver.core;

import com.example.mq.common.MqException;

import java.util.Queue;

/**
 * 使用这个类 实现交换机的转发规则
 * 借助这个类 验证 bindingKey 是否合法
 */
public class Router {

    // bindingKey 的构造规则:
    //1.数字,字母,下划线
    //2.使用.分割成若干部分
    //3.可以存在 * 和 # 作为通配符 但是只能作为一个独立的分段(由.分割的分段)
    public boolean checkBindingKey(String bindingKey){
        if(bindingKey.length()==0){
            //空字符串. 合法的情况 如 在使用 direct / fanout 交换机的时候 bindingKey 用不上 就可以设置成""
            return true;
        }
        for (int i = 0; i <bindingKey.length(); i++) {
            char ch=bindingKey.charAt(i);
            //判断该字符是不是大写字符
            if(ch >= 'A'&&ch <= 'z'){
                continue;
            }
            //判断该字符是不是小写字符
            if(ch >= 'a' && ch <= 'z'){
                continue;
            }
            //判断该字符是不是数字
            if(ch >= '0' && ch <= '9'){
                continue;
            }
            //判断是否是下划线 或者 .
            if(ch=='_'||ch=='.'||ch=='#'||ch=='*'){
                continue;
            }
            //该字符不是上述任何一种合法情况 直接返回false
            return false;
        }
        //检查 * / # 是不是独立的部分
        //aaa.*.bbb 合法
        //aaa.a*.bbb 非法情况
        String[] split = bindingKey.split("\\.");
        for (String word : split){
            //检查 word 长度如果大于1 并且 包含了 */# 即为非法
            if(word.length()>1&&(word.contains("*")||word.contains("#"))){
                 return false;
            }
        }
        //约定通配符 之间的相邻关系(认为约定)
        // 小声bb 前三种实现匹配的逻辑十分复杂 并且提升不大 我们就不实现了~
        //1.aaa.#.#.bbb => 非法
        //2.aaa.#.*.bbb => 非法
        //3.aaa.*.#.bbb => 非法
        //4.aaa.*.*.bbb => 合法
        for (int i = 0; i < split.length-1; i++) {
            //连续两个 # #
            if(split[i].equals("#")&&split[i+1].equals("#")){
                return false;
            }
            // # 连着 *
            if(split[i].equals("#")&&split[i+1].equals("*")){
                return false;
            }
            // * 连着 #
            if(split[i].equals("*")&&split[i+1].equals("#")){
                return false;
            }
        }
        return true;
    }

    //routingKey 的构造结果
    //1.数字,字母,下划线
    //2.使用.分割成若干部分
    public boolean checkRoutingKey(String routingKey){
        //检测是否合法
        if(routingKey.length()==0){
            //空字符串. 合法的情况 如 在使用 fanout 交换机的时候 routingKey 用不上 就可以设置成""
            return true;
        }
        for (int i = 0; i <routingKey.length(); i++) {
            char ch=routingKey.charAt(i);
            //判断该字符是不是大写字符
            if(ch >= 'A'&&ch <= 'z'){
                continue;
            }
            //判断该字符是不是小写字符
            if(ch >= 'a' && ch <= 'z'){
                continue;
            }
            //判断该字符是不是数字
            if(ch >= '0' && ch <= '9'){
                continue;
            }
            //判断是否是下划线 或者 .
            if(ch=='_'||ch=='.'){
                continue;
            }
            //该字符不是上述任何一种合法情况 直接返回false
            return false;
        }
        return true;
    }


    //判定当前消息 能不能转发给这个绑定 对应的队列
    public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {
        //根据不同的 exchangeType 来使用不同的判定转发规则
        if(exchangeType==ExchangeType.FANOUT){
            //如果是FANOUT类型 则该交换机上绑定的所有数据都可以转发
            return true;
        }else if(exchangeType==ExchangeType.TOPIC){
            //如果是TOPIC类型  则要校验绑定的 bindingKey 和消息的 routingKey
            return routeTopic(binding,message);
        }else {
            //其他情况 是不应该存在的
            throw  new MqException("[Router] 交换机类型非法! exchangeType="+exchangeType);
        }
    }

    private boolean routeTopic(Binding binding, Message message) {
        String bindingKey = binding.getBindingKey();
        String routingKey = message.getRoutingKey();
        //按照 . 切分
        String[] bindingTokens = bindingKey.split("\\.");
        String[] routingTokens = routingKey.split("\\.");
        //使用双指针
        int bindingIndex=0,routingIndex=0;
        while (bindingIndex<bindingTokens.length&&routingIndex<routingTokens.length){
            if(bindingTokens[bindingIndex].equals("*")){
                //[情况二]如果遇到 * 直接匹配到下一位
                bindingIndex++;
                routingIndex++;
            } else if(bindingTokens[bindingIndex].equals("#")){
                //[情况三]如果是 #
                bindingIndex++;
                if(bindingIndex==bindingTokens.length){
                    //说明该 # 后面没东西了 说明此时匹配成功
                    return true;
                }
                //[情况四]# 后面还有数据 拿bindingIndex 的下一位去 routingTokens找这个位置的值
                //找到后 将箭头指向这个位置的下一位
                //该方法 用来查询该部分在 routingTokens 中的位置 返回改下标 找不到就返回-1
               routingIndex = findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);
                if(routingIndex==-1){
                    //没找到匹配的结果 说明匹配失败
                    return false;
                }
                //找到了位置 继续往后匹配
                bindingIndex++;
                routingIndex++;
            }else {
                //[情况一]指向的是普通字符串 就必须和相同位置的字符串相同 否则就不匹配
                if(!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])){
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }

        }
        //[情况五]前面都匹配 但是一个到达末尾 另一个没到达末尾 也是不匹配 如果都到末尾了 说明匹配成功
        if((routingIndex ==routingTokens.length)&&(bindingIndex==bindingTokens.length)){
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        //查找在 routingTokens 数组 routingIndex 下标后  bindingToken[bindingIndex] 下标内容的下标位置
        for (int i = routingIndex; i <routingTokens.length; i++) {
            if(routingTokens[i].equals(bindingToken)){
                return i;
            }
        }
        return -1;
    }

}
